package server

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/labstack/echo/v4"
	"net/http"

	"v8s/logger"

	"github.com/labstack/echo-contrib/prometheus"
	"github.com/labstack/echo/v4/middleware"

	"go.uber.org/zap"
)

type (
	Server struct {
		*echo.Echo
		log *zap.SugaredLogger
	}
)

func RunServers(servers ...*Server) chan error {
	errs := make(chan error, len(servers))

	for _, currentServer := range servers {
		current := currentServer

		go func() {
			err := current.Start()
			if err != nil {
				errs <- err
			}
		}()
	}

	return errs
}

func NewServer(properties HttpServerProperties, registerPrometheus bool) *Server {
	var log = logger.Logger().Named("http_server").Sugar()

	if properties.Address == "" {
		panic(fmt.Errorf("server address must not be empty"))
	}

	service := echo.New()
	service.Server.Addr = properties.Address
	service.HideBanner = true
	service.HidePort = true

	config, err := properties.TLS.ToConfig()
	if err != nil {
		panic(fmt.Errorf("failed to load server tls config: %w", err))
	}

	service.Use(middleware.Logger())

	service.Use(middleware.BodyDump(func(context echo.Context, request []byte, response []byte) {
		if len(request) > 0 {
			log.Debugf("receivied request: %s", request)
			log.Debugf("send response: %s", response)
		}
	}))

	service.Use(func(block echo.HandlerFunc) echo.HandlerFunc {
		return func(context echo.Context) error {
			if context.IsTLS() {
				certificates := context.Request().TLS.PeerCertificates
				certificate := certificates[0]
				for _, current := range properties.PermitCNs {
					if current == certificate.Subject.CommonName {
						return block(context)
					}
				}

				log.Errorf("client with CN: '%v' is not authorized to access api", certificate.Subject.CommonName)
				return context.NoContent(http.StatusUnauthorized)
			}

			return block(context)
		}
	})

	if config != nil {
		service.TLSServer.Addr = properties.Address
		service.TLSServer.ReadTimeout = properties.ReadTimeout
		service.TLSServer.TLSConfig = config
		service.TLSServer.TLSConfig.ClientAuth = tls.RequireAnyClientCert
	}

	if registerPrometheus {
		p := prometheus.NewPrometheus("echo", nil)
		p.Use(service)
	}

	return &Server{
		Echo: service,
		log:  log,
	}
}

func (s *Server) Start() error {
	var server *http.Server

	if s.TLSServer.TLSConfig != nil {
		s.log.Info("starting TLS server")
		server = s.TLSServer
	} else {
		s.log.Info("starting server")
		server = s.Server
	}

	if err := s.StartServer(server); err != nil && !errors.Is(err, http.ErrServerClosed) {
		err = fmt.Errorf("listen and serve TLS error: %w", err)
		s.log.Error(err)

		return err
	}

	return nil
}
